#include "vcl_stl.h"

#include "bkbase.h"
#include "bkipc.h"
#include "bstcp.h"
#include "h_base.h"
#include "h_xml.h"
#include "h_lz77.h"
using namespace h_base;

#include "simbat.h"
#include "simbat_plugin.h"

#include <sys/timeb.h>

static const char *PASS_KEY = "sim bat 1.0.0   ";

namespace simbat
{

bool Read(int Fd, char *Buf, int Len)
{
   int i, r;

   for( i = 0; ; )
   {
      r = read(Fd, Buf + i, Len - i);
      if( r <= 0 )
         return false;
      i += r;
      if( i == Len )
         break;
   }

   return true;
}

int ReadFromSocketHSL(int Sock, void *Buf, int MaxLen, int MilliSecTimeOut)
{
   char TmpStr[80 + 1];
   int r;
   int Len;

   r = BsReadFromSocket(Sock, TmpStr, 8, MilliSecTimeOut);

   if ( r <= 0 )
   {
      return r;
   }

   TmpStr[r] = '\0';
   Len = BkHexStrToInt(TmpStr);

   if ( Len > MaxLen )
   {
      r = BsReadFromSocket(Sock, Buf, MaxLen, MilliSecTimeOut);
      if ( r <= 0 )
      {
         return r;
      }
      int TotalLen;
      int FirstLen = r;
      for ( TotalLen = Len - FirstLen; TotalLen > 0; )
      {
         int CurrentLen = TotalLen > 1024 ? 1024 : TotalLen;
         char TmpBuf[1024 + 1];
         r = BsReadFromSocket(Sock, TmpBuf, CurrentLen, MilliSecTimeOut);
         if ( r <= 0 )
         {
            break;
         }
         TotalLen -= r;
      }
      return FirstLen;
   }
   else
   {
      r = BsReadFromSocket(Sock, Buf, Len, MilliSecTimeOut);
      return r;
   }
}

int WriteToSocketHSL(int Sock, const void *Buf, int Len, int MilliSecTimeOut)
{
   char LenBuf[200 + 1];
   int r;

   snprintf(LenBuf, 200, "%08lx", (unsigned long)Len);

   r = BsWriteToSocket2(Sock, LenBuf, 8, Buf, Len, MilliSecTimeOut);
   return r;
}

void SetBaseByPath(const char *Path)
{
   char BinPath[500 + 1];

   if ( Path[0] == '/' )
   {
      BkStrNCpy(BinPath, Path, sizeof(BinPath) - 1);
   }
   else
   {
      if ( memcmp(Path, "./", 2) == 0 )
      {
         snprintf(BinPath, 500, "%s/%s", getenv("PWD"), Path + 2);
      }
      else
      {
         snprintf(BinPath, 500, "%s/%s", getenv("PWD"), Path);
      }
   }

   int i = strlen(BinPath);

   for ( ; i > 0 && BinPath[i - 1] != '/'; i-- )
      ;

   if ( i > 0 )
   {
      i--;
   }

   for ( ; i > 0 && BinPath[i - 1] != '/'; i-- )
      ;

   if ( i > 0 )
   {
      i--;
   }

   BinPath[i] = '\0';
   setenv("SIMBAT_BASE", BinPath, 1);
}

void HSyslog::Setup(const HString &LogDir, const HString &LevelStr)
{
   FLogDir = LogDir;
   FLevel = StringToLogLevel(LevelStr);
}

bool HSyslog::CheckLevel(HLogLevel Level)
{
   if ( Level < FLevel )
   {
      return false;
   }

   return true;
}

void HSyslog::Log(const char *SourceFile, int SourceLine,
   HLogLevel Level, const char *LogPoint, const char *Msg)
{
   if ( !CheckLevel(Level) )
   {
      return;
   }

   HFileLockWrite Lock("$SIMBAT_BASE/tmp/syslog.lck");

   struct tm    tm;
   struct timeb tp;

   ftime(&tp);
   BkLocalTime(&tp.time, &tm);

   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/console_%04d%02d%02d.log",
      FLogDir.c_str(), tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);

   FILE *f = fopen(FileName, "at");

   if ( f == NULL )
   {
      BkPrintError("Can not create file %s, errno = %d", FileName, errno);
      BkPrintError("%s, %d : %s", SourceFile, SourceLine, Msg);
      return;
   }

   pid_t Pid = getpid();

// Level : DEBUG, INFO, WARN, ERROR, FATAL
// LogPoint : ENTRY, EXIT, CALLS, CALLE, ONERR, OTHER

   fprintf(f, "[%04d-%02d-%02d %02d:%02d:%02d.%03d][%-5s]"
      "[%8d][%-5s][%s][{\"LINE\":\"%d\",\"MSG\":\"",
      tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
      tm.tm_hour, tm.tm_min, tm.tm_sec, tp.millitm,
      LogLevelString(Level), getpid(), LogPoint, SourceFile, SourceLine);

   char NewMsg[16384 + 1];

   {
      int i;
      const char *s = Msg;
      for ( i = 0; *s != '\0' && i < 16000; )
      {
         if ( *s == '"' || *s == '\\' )
         {
            NewMsg[i++] = '\\';
         }
         NewMsg[i++] = *s++;
      }
      NewMsg[i] = '\0';
   }

   fprintf(f, "%s\"}]\n", NewMsg);
   fclose(f);
}

const char *HSyslog::LogLevelString(HLogLevel Level)
{
   static const char *StringArr[] =
   {
      "DEBUG",
      "INFO",
      "WARN",
      "ERROR",
      "FATAL"
   };

   return StringArr[Level];
}

HLogLevel HSyslog::StringToLogLevel(const HString &Str)
{
   if ( Str == "DEBUG" )
   {
      return llDebug;
   }

   if ( Str == "INFO" )
   {
      return llInfo;
   }

   if ( Str == "WARN" )
   {
      return llWarn;
   }

   if ( Str == "ERROR" )
   {
      return llError;
   }

   return llFatal;
}

HFileLockRead::HFileLockRead(const char *FileName)
   : FLockFd(-1), FLocked(false)
{
   FLockFd = BkOpenLockFile(EnvEvaluate(FileName).c_str());

   Lock();
}

HFileLockRead::~HFileLockRead()
{
   if ( FLocked )
   {
      Unlock();
   }

   if ( FLockFd != -1 )
   {
      close(FLockFd);
      FLockFd = -1;
   }
}

void HFileLockRead::Lock()
{
   BkReadLock(FLockFd, 0, 0, BK_TIME_INFINITE);
   FLocked = true;
}

void HFileLockRead::Unlock()
{
   BkUnlock(FLockFd, 0, 0);
   FLocked = false;
}

HFileLockWrite::HFileLockWrite(const char *FileName)
   : FLockFd(-1), FLocked(false)
{
   FLockFd = BkOpenLockFile(EnvEvaluate(FileName).c_str());

   Lock();
}

HFileLockWrite::~HFileLockWrite()
{
   if ( FLocked )
   {
      Unlock();
   }

   if ( FLockFd != -1 )
   {
      close(FLockFd);
      FLockFd = -1;
   }
}

void HFileLockWrite::Lock()
{
   BkWriteLock(FLockFd, 0, 0, BK_TIME_INFINITE);
   FLocked = true;
}

void HFileLockWrite::Unlock()
{
   BkUnlock(FLockFd, 0, 0);
   FLocked = false;
}

static const char *PVER = "SIMJIE0104";

bool CallServer(HSyslog &Syslog, int MilliSecTimeOut, const HString &IpPort, const char *Func, const HString &Object,
   const char *RequestStr, bool SimpleResult, HString &Result,
   bool WillRetry, bool *UnknownFlag, bool *PSuccFlag)
{
   int r;
   char Buf[8192 + 1];
   HString ServerIp;
   int ServerPort;
   HRaw Raw;

   if ( UnknownFlag != NULL )
   {
      *UnknownFlag = false;
   }

   snprintf(Buf, 8192, "CallServer(MilliSecTimeOut = %d, IpPort = %s, Func = %s, Object = %s, "
      "RequestStr = %s, SimpleResult = %d, WillRetry = %d",
      MilliSecTimeOut, IpPort.c_str(), Func, Object.c_str(), RequestStr, SimpleResult, WillRetry);
   Syslog.Log(__FILE__, __LINE__, llDebug, "CALLS", Buf);

   if ( PSuccFlag != NULL )
   {
      *PSuccFlag = false;
   }

   Result = "";

   int Sock = -1;

   if ( memcmp(IpPort.c_str(), "UD:", 3) == 0 )
   {
      Sock = BsCreateClientSocketUD(IpPort.c_str() + 3, MilliSecTimeOut);
      Raw.resize(strlen(RequestStr));
      snprintf(Raw.data(), Raw.size() + 1, "%s", RequestStr);
   }
   else
   {
      Raw.resize(strlen(PVER) + strlen(RequestStr));
      snprintf(Raw.data(), Raw.size() + 1, "%s%s", PVER, RequestStr);
      SplitIpPort(IpPort, ServerIp, ServerPort);
      Sock = BsCreateClientSocket(ServerIp.c_str(), ServerPort, MilliSecTimeOut);
   }

   if ( Sock < 0 )
   {
      snprintf(Buf, 8192, "%sCallServer(%s, %s, %s), Connect to fail, r = %d, errno = %d",
         WillRetry ? "SIMBAT_CHECK : " : "", IpPort.c_str(), Func, Object.c_str(), Sock, errno);
      Syslog.Log(__FILE__, __LINE__, WillRetry ? llInfo : llError, "ONERR", Buf);
      Result = Buf;
      snprintf(Buf, 8192, "CallServer fail, Result = %s", Result.c_str());
      Syslog.Log(__FILE__, __LINE__, llDebug, "CALLE", Buf);
      return false;
   }

   r = WriteToSocketHSL(Sock, Raw.data(), Raw.size(), MilliSecTimeOut);

   if ( r < 0 )
   {
      snprintf(Buf, 8192, "%sCallServer(%s, %s, %s), BsWriteToSocket error, r = %d, errno = %d",
         WillRetry ? "SIMBAT_CHECK : " : "", IpPort.c_str(), Func, Object.c_str(),
         r, errno);
      Syslog.Log(__FILE__, __LINE__, WillRetry ? llInfo : llError, "ONERR", Buf);
      Result = Buf;
      snprintf(Buf, 8192, "CallServer fail, Result = %s", Result.c_str());
      Syslog.Log(__FILE__, __LINE__, llDebug, "CALLE", Buf);
      close(Sock);
      return false;
   }

   r = BsReadFromSocket(Sock, Buf, 1, MilliSecTimeOut);

   if ( r <= 0 )
   {
      snprintf(Buf, 8192, "%sCallServer(%s, %s, %s), BsReadFromSocket error, r = %d, errno = %d",
         WillRetry ? "SIMBAT_CHECK : " : "", IpPort.c_str(), Func, Object.c_str(),
         r, errno);
      Syslog.Log(__FILE__, __LINE__, WillRetry ? llInfo : llError, "ONERR", Buf);
      Result = Buf;
      snprintf(Buf, 8192, "CallServer fail, Result = %s", Result.c_str());
      Syslog.Log(__FILE__, __LINE__, llDebug, "CALLE", Buf);
      close(Sock);
      if ( UnknownFlag != NULL )
      {
         *UnknownFlag = true;
      }
      return false;
   }

   bool SuccFlag = Buf[0] == '0';

   if ( !SuccFlag || !SimpleResult )
   {
      r = ReadFromSocketHSL(Sock, Buf, 8192, MilliSecTimeOut);
      if ( r < 0 )
      {
         snprintf(Buf, 8192, "%sCallServer(%s, %s, %s), BsReadFromSocketHSL error, r = %d, errno = %d",
            WillRetry ? "SIMBAT_CHECK : " : "", IpPort.c_str(), Func, Object.c_str(),
            r, errno);
         Syslog.Log(__FILE__, __LINE__, WillRetry ? llInfo : llError, "ONERR", Buf);
         Result = Buf;
         snprintf(Buf, 8192, "CallServer fail, Result = %s", Result.c_str());
         Syslog.Log(__FILE__, __LINE__, llDebug, "CALLE", Buf);
         close(Sock);
         return false;
      }
   }

   Buf[r] = '\0';
   Result = Buf;
   snprintf(Buf, 8192, "CallServer %s, Result = %s", SuccFlag ? "success" : "fail", Result.c_str());
   Syslog.Log(__FILE__, __LINE__, llDebug, "CALLE", Buf);
   close(Sock);

   if ( PSuccFlag == NULL )
   {
      return SuccFlag;
   }
   else
   {
      *PSuccFlag = SuccFlag;
      return true;
   }
}     

bool GetLineFromFile(HSyslog &Syslog, char ErrMsg[2048], const char *FileName, char *Line, bool SkipError)
{
   FILE *f = fopen(FileName, "rt");

   if ( f == NULL )
   {
      if ( !SkipError )
      {
         snprintf(ErrMsg, 2048, "Can not open %s, errno = %d", FileName, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return false;
      }
      else
      {
         Line[0] = '\0';
         return true;
      }
   }

   int r;

   r = ReadLine(f, Line, 200);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "ReadLine(%s) fail, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      fclose(f);
      return false;
   }

   Line[r] = '\0';
   fclose(f);
   return true;
}

bool GetNumberFromFile(HSyslog &Syslog, char ErrMsg[2048], const char *FileName, int &Number, bool SkipError)
{
   char Line[200 + 1];

   if ( !GetLineFromFile(Syslog, ErrMsg, FileName, Line, SkipError) )
   {
      return false;
   }

   Number = StrToInt(Line);
   return true;
}

bool PutLineToFile(HSyslog &Syslog, char ErrMsg[2048], const char *FileName, const char *Line)
{
   FILE *f = fopen(FileName, "wt");

   if ( f == NULL )
   {
      snprintf(ErrMsg, 2048, "Can not create %s, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   fprintf(f, "%s\n", Line);
   fclose(f);
   return true;
}

bool PutNumberToFile(HSyslog &Syslog, char ErrMsg[2048], const char *FileName, int Number)
{
   char Line[200 + 1];

   snprintf(Line, 200, "%015d\n", Number);

   int Fd  = open(FileName, O_CREAT|O_WRONLY|O_SYNC, 0666);

   if ( Fd < 0 )
   {
      snprintf(ErrMsg, 2048, "Can not create %s, errno = %d", FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   if ( write(Fd, Line, 16) < 0 )
   {
      snprintf(ErrMsg, 2048, "write fail, errno = %d", errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Fd);
      return false;
   }

   if ( close(Fd) < 0 )
   {
      snprintf(ErrMsg, 2048, "close fail, errno = %d", errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   return true;
}

bool SafeSaveXmlToFile(HSyslog &Syslog, char ErrMsg[2048], HXmlTree &Xml, const char *FileName)
{
   char OldFileName[2048];
   char NewFileName[2048];

   snprintf(OldFileName, 500, "%s_old", FileName);
   snprintf(NewFileName, 500, "%s_new", FileName);
   unlink(OldFileName);
   unlink(NewFileName);

   if ( !Xml.SaveToFile(NewFileName) )
   {
      snprintf(ErrMsg, 2048, "SaveToFile(%s) fail, %s", NewFileName, Xml.GetErrorString().c_str());
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   rename(FileName, OldFileName);

   if ( rename(NewFileName, FileName) == -1 )
   {
      snprintf(ErrMsg, 2048, "rename(%s, %s) fail, errno = %d", NewFileName, FileName, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return false;
   }

   return true;
}

HJobType StringToJobType(const HString &String)
{
   if ( String == "JOB" )
   {
      return jtJob;
   }
   else if ( String == "CONNECTOR" )
   {
      return jtConnector;
   }
   else if ( String == "EXTERN_JOB" )
   {
      return jtExternJob;
   }
   else if ( String == "EVENT_WAIT" )
   {
      return jtEventWait;
   }
   else if ( String == "EVENT_FIRE" )
   {
      return jtEventFire;
   }
   else if ( String == "GROUP" )
   {
      return jtGroup;
   }
   else if ( String == "TIME" )
   {
      return jtTime;
   }
   else if ( String == "BATCH" )
   {
      return jtBatch;
   }
   else
   {
      return jtJob;
   }
}

HString JobTypeToString(HJobType Type)
{
   switch ( Type )
   {
   case jtJob :
      return "JOB";
   case jtConnector :
      return "CONNECTOR";
   case jtExternJob :
      return "EXTERN_JOB";
   case jtEventWait :
      return "EVENT_WAIT";
   case jtEventFire :
      return "EVENT_FIRE";
   case jtGroup :
      return "GROUP";
   case jtTime :
      return "TIME";
   case jtBatch :
      return "BATCH";
   default :
      return "JOB";
   }
}

static const char *JobStatusStringArr[] =
{
   "WAITING",
   "RUNNING",
   "SKIPPED",
   "FAIL",
   "SUCCESS",
   "UNKNOWN",
};

const char *JobStatusToString(HJobStatus Status)
{
   if ( Status > jsUnknown )
   {
      Status = jsUnknown;
   }

   return JobStatusStringArr[Status];
}

HJobStatus StringToJobStatus(const char *String)
{
   for ( int i = jsBEGIN; i <= jsUnknown; i++ )
   {
      if ( strcmp(String, JobStatusStringArr[i]) == 0 )
      {
         return (HJobStatus)i;
      }
   }

   return jsUnknown;
}

bool GetMyId(const char *SimbatBase, int &MyId, HNodeRole &Role, char ErrStr[500 + 1])
{
   char FileName[500 + 1];
   char Line[500 + 1];
   FILE *f = NULL;

   snprintf(FileName, 500, "%s/etc/my_id", SimbatBase);
   f = fopen(FileName, "rt");

   if ( f == NULL )
   {
      MyId = 0;
      Role = nrMaster;
      return true;
   }

   if ( ReadLine(f, Line, 500) == -1 )
   {
      snprintf(ErrStr, 500, "my_id is empty");
      fclose(f);
      return false;
   }

   fclose(f);

   int Pos = 0;
   char w[80 + 1];

   Pos += ReadWord(Line + Pos, w, " ", 80);
   MyId = StrToInt(w) - 1;

   if ( MyId < 0 )
   {
      snprintf(ErrStr, 500, "my_id(%s) syntax error", Line);
      return false;
   }

   Pos += ReadWord(Line + Pos, w, " ", 80);

   if ( strcmp(w, "master") == 0 )
   {
      Role = nrMaster;
   }
   else if ( strcmp(w, "slave") == 0 )
   {
      Role = nrSlave;
   }
   else if ( strcmp(w, "backup") == 0 )
   {
      Role = nrBackup;
   }
   else if ( strcmp(w, "gateway") == 0 )
   {
      Role = nrGateway;
   }
   else
   {
      snprintf(ErrStr, 500, "my_id(%s) syntax error", Line);
      return false;
   }

   if ( ( MyId == 0 && Role != nrMaster && Role != nrBackup && Role != nrGateway ) ||
        ( MyId > 0 && Role != nrSlave ) )
   {
      snprintf(ErrStr, 500, "my_id(%s) role not valid", Line);
      return false;
   }

   return true;
}

bool SendBackupFile(HSyslog &Syslog, const HString &ShrDir, const HString &LogDir, const char *SimbatBase,
   const char *IpAddr, int Port, HBackupObjType Type, const char *ObjName, long Offset, long Len, const char *ShellFlag)
{
   char ErrMsg[2048];

   snprintf(ErrMsg, 2048, "SendBackupFile begin, Addr = %s:%d, Type = %d, ObjName = %s, Offset = %ld, Len = %ld, ShellFlag = %s",
      IpAddr, Port, Type, ObjName, Offset, Len, ShellFlag);
   Syslog.Log(__FILE__, __LINE__, llDebug, "ENTRY", ErrMsg);

   char FileName[500 + 1];

   switch ( Type )
   {
   case boBatchStatus :
      snprintf(FileName, 500, "%s/status/batch_status", ShrDir.c_str());
      break;
   case boJobEvents :
      snprintf(FileName, 500, "%s/status/job_events", ShrDir.c_str());
      break;
   case boCtldat :
      snprintf(FileName, 500, "%s/status/control.dat", ShrDir.c_str());
      break;
   case boVarpool :
      snprintf(FileName, 500, "%s/status/var_pool", ShrDir.c_str());
      break;
   case boConfig :
      snprintf(FileName, 500, "%s/etc/config_cust.xml", ShrDir.c_str());
      break;
   case boCurrentSbt :
      snprintf(FileName, 500, "%s/status/current.sbt", ShrDir.c_str());
      break;
   case boEvent :
      snprintf(FileName, 500, "%s/events/%s", ShrDir.c_str(), ObjName);
      break;
   case boJobLog :
      snprintf(FileName, 500, "%s/%s", LogDir.c_str(), ObjName);
      break;
   case boShr :
      snprintf(FileName, 500, "%s/%s", ShrDir.c_str(), ObjName);
      break;
   case boAuth :
      snprintf(FileName, 500, "%s/etc/auth.xml", ShrDir.c_str());
      break;
   default :
      snprintf(FileName, 500, "%s/%s", SimbatBase, ObjName);
      break;
   }

   FILE *f = NULL;

   if ( Len > 0 )
   {
      f = fopen(FileName, "rb");
      if ( f == NULL )
      {
         snprintf(ErrMsg, 2048, "Can not open %s, errno = %d", FileName, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         return false;
      }
   }

   int MilliSecTimeOut = 20000;
   int Sock = BsCreateClientSocket(IpAddr, Port, MilliSecTimeOut);

   if ( Sock < 0 )
   {
      snprintf(ErrMsg, 2048, "Connect to(%s:%d) fail, r = %d, errno = %d",
         IpAddr, Port, Sock, errno);
      Syslog.Log(__FILE__, __LINE__, Type == boJobLog ? llWarn : llError, "ONERR", ErrMsg);
      if ( f != NULL )
      {
         fclose(f);
      }
      return false;
   }

   int r;
   char Buf[500 + 1];

   snprintf(Buf, 500, "%sm|||%d|%s|%ld|%ld|%s", PVER, Type, ObjName, Offset, Len, ShellFlag);
   r = WriteToSocketHSL(Sock, Buf, strlen(Buf), MilliSecTimeOut);

   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "WriteToSocketHSL fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      if ( f != NULL )
      {
         fclose(f);
      }
      return false;
   }

   r = BsReadFromSocket(Sock, Buf, 1, MilliSecTimeOut);

   if ( r <= 0 )
   {
      snprintf(ErrMsg, 2048, "ReadFromSocket(%s:%d) fail, r = %d, errno = %d", IpAddr, Port, r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      if ( f != NULL )
      {
         fclose(f);
      }
      return false;
   }

   if ( Buf[0] != '0' )
   {
      r = ReadFromSocketHSL(Sock, Buf, 200, MilliSecTimeOut);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "ReadFromSocketHSL fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Sock);
         if ( f != NULL )
         {
            fclose(f);
         }
         return false;
      }
      Buf[r] = '\0';
      close(Sock);
      snprintf(ErrMsg, 2048, "SendBackupFile fail, Reason = %s", Buf);
      Syslog.Log(__FILE__, __LINE__, llError, "EXIT", ErrMsg);
      if ( f != NULL )
      {
         fclose(f);
      }
      return false;
   }

   if ( Len <= 0 )
   {
      close(Sock);
      return true;
   }

   if ( Offset > 0 )
   {
      fseek(f, Offset, SEEK_SET);
   }

   for ( ; Len > 0; )
   {
      int Len1 = ( Len >= 2048 ? 2048 : Len );
      char Buf1[2048 + 9];
      char Buf2[4096 + 9];
      int Len2 = 0;
      bool CompressFlag = false;
      fread(Buf1 + 9, Len1, 1, f);
      if ( Len1 >= 200 )
      {
         Lz77CompressBuf(Buf1 + 9, Buf2 + 9, Len1, &Len2);
         if ( Len2 < Len1 / 2 )
         {
            CompressFlag = true;
         }
      }
      if ( CompressFlag )
      {
         snprintf(Buf, 500, "Y%08x", Len2);
         memcpy(Buf2, Buf, 9);
         r = BsWriteToSocket(Sock, Buf2, Len2 + 9, MilliSecTimeOut);
      }
      else
      {
         snprintf(Buf, 500, "N%08x", Len1);
         memcpy(Buf1, Buf, 9);
         r = BsWriteToSocket(Sock, Buf1, Len1 + 9, MilliSecTimeOut);
      }
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "WriteToSocket fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         fclose(f);
         close(Sock);
         return false;
      }
      Len -= Len1;
   }

   fclose(f);
   r = BsWriteToSocket(Sock, "E00000000", 9, MilliSecTimeOut);
   if ( r < 0 )
   {
      snprintf(ErrMsg, 2048, "WriteToSocket fail, r = %d, errno = %d", r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      return false;
   }

   r = BsReadFromSocket(Sock, Buf, 1, MilliSecTimeOut);

   if ( r <= 0 )
   {
      snprintf(ErrMsg, 2048, "ReadFromSocket(%s:%d) fail, r = %d, errno = %d", IpAddr, Port, r, errno);
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      close(Sock);
      return false;
   }

   if ( Buf[0] != '0' )
   {
      r = ReadFromSocketHSL(Sock, Buf, 200, MilliSecTimeOut);
      if ( r < 0 )
      {
         snprintf(ErrMsg, 2048, "ReadFromSocketHSL fail, r = %d, errno = %d", r, errno);
         Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         close(Sock);
         return false;
      }
      Buf[r] = '\0';
      close(Sock);
      snprintf(ErrMsg, 2048, "SendBackupFile fail, Reason = %s", Buf);
      Syslog.Log(__FILE__, __LINE__, llError, "EXIT", ErrMsg);
      return false;
   }

   close(Sock);
   return true;
}

bool LoadIniFile(const char *FileName, vector<HIniSection> &SectionVec, char ErrMsg[2048], bool CommonFlag)
{
   int LineNo = 0;
   bool UnreadFlag = false;
   bool EofFlag = false;
   char Line[4096 + 1];
   FILE *f = fopen(FileName, "rt");

   if ( f == NULL )
   {
      snprintf(ErrMsg, 2048, "Can not open %s, errno = %d", FileName, errno);
      return false;
   }

   if ( CommonFlag )
   {
      SectionVec.clear();
   }

#define INI_READ_LINE \
      if ( UnreadFlag )\
      {\
         UnreadFlag = false;\
      }\
      else\
      {\
         if ( ReadLine(f, Line, 4096) == -1 )\
         {\
            EofFlag = true;\
            break;\
         }\
         LineNo++;\
         BkRightTrim(Line);\
      }\
      if ( Line[0] == '#' || Line[0] == '\0' )\
      {\
         continue;\
      }

   for ( ; !EofFlag; )
   {
      INI_READ_LINE
      int Pos = 0;
      for ( ; Line[Pos] == ' '; Pos++ )
         ;
      int Len = strlen(Line);
      if ( Line[Pos] != '[' || Line[Len - 1] != ']' )
      {
         snprintf(ErrMsg, 2048, "Line %d, '[]' not found", LineNo);
         fclose(f);
         return false;
      }
      HIniSection Section;
      Section.Name = HString(Line + Pos + 1, Len - Pos - 2);
      for ( ; !EofFlag; )
      {
         INI_READ_LINE
         Pos = 0;
         char w[80 + 1];
         Pos += BkReadWord(Line + Pos, w, " =[", 80);
         if ( w[0] == '[' )
         {
            UnreadFlag = true;
            break;
         }
         HString Key = w;
         HString Value;
         Pos += BkReadWord(Line + Pos, w, " =[", 80);
         if ( w[0] != '=' )
         {
            snprintf(ErrMsg, 2048, "Line %d, '=' not found", LineNo);
            fclose(f);
            return false;
         }
         for ( ; Line[Pos] == ' '; Pos++ )
            ;
         bool EndFlag = false;
         for ( ; !EndFlag; )
         {
            Len = strlen(Line);
            if ( Line[Len - 1] == '\\' )
            {
               Value += HString(Line + Pos, Len - Pos - 1);
               Value += "\n";
               EndFlag = false;
               INI_READ_LINE
               Pos = 0;
            }
            else
            {
               Value += HString(Line + Pos, Len - Pos);
               EndFlag = true;
            }
         }
         RightTrim(Value);
         Section.ItemMap[Key] = Value;
      }
      if ( ( CommonFlag && Section.Name == "common" ) ||
           ( !CommonFlag && Section.Name != "common" ) )
      {
         SectionVec.push_back(Section);
      }
   }

   fclose(f);
   return true;
}

bool FileExist(const HString &Path)
{
   struct stat Stat;

   return stat(Path.c_str(), &Stat) != -1;
}

bool ForceMkdir(const HString &Path)
{
   if ( FileExist(Path) )
   {
      return true;
   }

   return mkdir(Path.c_str(), 0750) == 0;
}

bool CopyFile(const char *SrcFile, const char *DesFile, char ErrStr[500 + 1])
{
   int SrcFd = -1;
   int DesFd = -1;

   SrcFd = open(SrcFile, O_RDONLY);

   if ( SrcFd == -1 )
   {
      snprintf(ErrStr, 500, "Can not open %s, errno = %d", SrcFile, errno);
      return false;
   }

   DesFd = open(DesFile, O_CREAT | O_WRONLY | O_TRUNC | O_SYNC, 0666);

   if ( DesFd == -1 )
   {
      snprintf(ErrStr, 500, "Can not create %s, errno = %d", DesFile, errno);
      close(SrcFd);
      return false;
   }

   for ( ; ; )
   {
      char Buf[2048];
      int r = read(SrcFd, Buf, 2048);
      if ( r <= 0 )
      {
         break;
      }
      if ( write(DesFd, Buf, r) == -1 )
      {
         snprintf(ErrStr, 500, "write %s fail, errno = %d", DesFile, errno);
         close(DesFd);
         close(SrcFd);
         return false;
      }
   }

   if ( close(DesFd) == -1 )
   {
      snprintf(ErrStr, 500, "close %s fail, errno = %d", DesFile, errno);
      close(SrcFd);
      return false;
   }

   close(SrcFd);
   return true;
}

bool HCheckBase::Load(const char *FileName, char ErrMsg[2048])
{
   if ( !Xml.LoadFromFile(FileName) )
   {
      snprintf(ErrMsg, 2048, "Load %s fail, %s", FileName, Xml.GetErrorString().c_str());
      return false;
   }

   {
      vector<HString> IncludeFiles;
      Xml.GetValues("INCLUDE_FILES", IncludeFiles);
      for ( int i = 0; i < IncludeFiles.size(); i++ )
      {
         char TmpStr[500 + 1];
         char FileName[500 + 1];
         snprintf(TmpStr, 500, "%s", IncludeFiles[i].c_str());
         BkRightTrim(TmpStr);
         BkToLower(TmpStr);
         snprintf(FileName, 500, "%s/bats/%s.sbt", SimbatBase, TmpStr);
         HXmlTree TmpXml;
         if( !TmpXml.LoadFromFile(FileName) )
         {
            snprintf(ErrMsg, 2048, "Load %s fail, %s", FileName, Xml.GetErrorString().c_str());
            return false;
         }
         vector<HXmlTree*> Groups;
         TmpXml.GetChildren("GROUP", Groups);
         for ( int j = 0; j < Groups.size(); j++ )
         {
            TmpXml.MoveChildTo(Groups[j], &Xml);
         }
      }
   }

   vector<HXmlTree*> GroupVec;

   Graph.clear();
   GroupMap.clear();
   GroupNameVec.clear();
   AgentMap.clear();
   Xml.GetChildren("GROUP", GroupVec);

   for ( int i = 0; i < GroupVec.size(); i++ )
   {
      HXmlTree *GroupXml = GroupVec[i];
      HString GroupName = GroupXml->GetPropertyUpper("NAME");
      GroupNameVec.push_back(GroupName);
      if ( GroupMap.find(GroupName) != GroupMap.end() )
      {
         snprintf(ErrMsg, 2048, "Duplicate group : %s\n", GroupName.c_str());
         return false;
      }
      if ( i == 0 )
      {
         MainGroupName = GroupName;
      }
      GroupMap[GroupName] = GroupXml;
      vector<HXmlTree*> JobVec;
      GroupXml->GetChildren("JOB", JobVec);
      for ( int j = 0; j < JobVec.size(); j++ )
      {
         HXmlTree *JobXml = JobVec[j];
         job_info Job;
         Job.Name = GroupName + JobNameDelimiter + JobXml->GetPropertyUpper("NAME");
         Job.GroupId = GroupName;
         Job.Type = StringToJobType(JobXml->GetPropertyUpper("TYPE"));
         Job.Command = JobXml->GetProperty("COMMAND");
         Job.Tag = JobXml->GetProperty("TAG");
         Job.PartitionFile = JobXml->GetProperty("PARTITION_FILE");
         JobXml->GetValues("DESCRIPTION", Job.Description);
         vector<HString> StrVec;
         JobXml->GetValues("RESOURCE_LIST", StrVec);
         for ( int k = 0; k < StrVec.size(); k++ )
         {
            const char *s = StrVec[k].c_str();
            int Pos = 0;
            char w[500 + 1];
            bool ReadFlag = false;
            HString Name;
            Pos += ReadWord(s + Pos, w, " ,", 500);
            ToUpper(w);
            Name = w;
            Pos += ReadWord(s + Pos, w, " ,", 500);
            if ( w[0] == ',' )
            {
               Pos += ReadWord(s + Pos, w, " ,", 500);
               ToUpper(w);
               if ( w[0] == 'R' )
               {
                  ReadFlag = true;
               }
            }
            if ( ReadFlag )
            {
               if ( Job.ResourceSetWrite.find(Name) == Job.ResourceSetWrite.end() )
               {
                  Job.ResourceSetRead.insert(Name);
               }
            }
            else
            {
               if ( Job.ResourceSetRead.find(Name) != Job.ResourceSetRead.end() )
               {
                  Job.ResourceSetRead.erase(Name);
               }
               Job.ResourceSetWrite.insert(Name);
            }
         }
         if ( Job.Type == jtExternJob )
         {
            Job.ExternJob = JobXml->GetPropertyUpper("EXTERN_GROUP");
            HString Name = JobXml->GetPropertyUpper("EXTERN_JOB");
            if ( Name != "" )
            {
               Job.ExternJob = Job.ExternJob + JobNameDelimiter + Name;
            }
         }
         else
         {
            Job.ExternJob = "";
         }
         {
            Job.ExternJobList.clear();
            vector<HString> StrVec;
            JobXml->GetValues("EXTERN_JOB_LIST", StrVec);
            for ( int i = 0; i < StrVec.size(); i++ )
            {
               const char *s = StrVec[i].c_str();
               char w[500 + 1];
               int Pos = 0;
               Pos += ReadWord(s + Pos, w, " :", 500);
               ToUpper(w);
               HString ExternGroup = w;
               Pos += ReadWord(s + Pos, w, " :", 500);
               if ( w[0] == ':' )
               {
                  Pos += ReadWord(s + Pos, w, " :", 500);
                  ToUpper(w);
                  HString ExternJob = w;
                  Job.ExternJobList.push_back(ExternGroup + JobNameDelimiter + ExternJob);
               }
               else
               {
                  Job.ExternJobList.push_back(ExternGroup);
               }
            }
         }
         if( Job.Type == jtGroup )
            Job.Group = JobXml->GetPropertyUpper("GROUP");
         else
            Job.Group = "";
         if ( Graph.find(Job.Name) != Graph.end() )
         {
            snprintf(ErrMsg, 2048, "Duplicate job : %s\n", Job.Name.c_str());
            return false;
         }
         Graph[Job.Name] = Job;
      }
   }

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      for ( int i = 0; i < Job.ExternJobList.size(); i++ )
      {
         HString &ExternJob = Job.ExternJobList[i];
         if ( strstr(ExternJob.c_str(), JobNameDelimiter.c_str()) )
         {
            if ( Graph.find(ExternJob) == Graph.end() || ExternJob == Job.Name )
            {
               snprintf(ErrMsg, 2048, "Invalid extern : %s, %s",
                  Job.Name.c_str(), ExternJob.c_str());
               return false;
            }
         }
         else
         {
            if ( GroupMap.find(ExternJob) == GroupMap.end() ||
                 ExternJob == MainGroupName || ExternJob == Job.GroupId )
            {
               snprintf(ErrMsg, 2048, "Invalid extern : %s, %s",
                  Job.Name.c_str(), ExternJob.c_str());
               return false;
            }
         }
      }
      if ( Job.Type == jtExternJob )
      {
         if ( strstr(Job.ExternJob.c_str(), JobNameDelimiter.c_str()) )
         {
            if ( Graph.find(Job.ExternJob) == Graph.end() || Job.ExternJob == Job.Name )
            {
               snprintf(ErrMsg, 2048, "Invalid extern : %s, %s",
                  Job.Name.c_str(), Job.ExternJob.c_str());
               return false;
            }
         }
         else
         {
            if ( GroupMap.find(Job.ExternJob) == GroupMap.end() ||
                 Job.ExternJob == MainGroupName || Job.ExternJob == Job.GroupId )
            {
               snprintf(ErrMsg, 2048, "Invalid extern : %s, %s",
                  Job.Name.c_str(), Job.ExternJob.c_str());
               return false;
            }
         }
      }
      if ( Job.Type == jtGroup )
      {
         if ( GroupMap.find(Job.Group) == GroupMap.end() ||
              Job.Group == MainGroupName || Job.Group == Job.GroupId )
         {
            snprintf(ErrMsg, 2048, "Invalid group agent : %s, %s",
               Job.Name.c_str(), Job.Group.c_str());
            return false;
         }
         if ( AgentMap.find(Job.Group) == AgentMap.end() )
         {
            AgentMap[Job.Group] = Job.GroupId;
         }
         else
         {
            snprintf(ErrMsg, 2048, "Duplicate group agent : %s, %s; %s",
               Job.Name.c_str(), Job.Group.c_str(), AgentMap[Job.Group].c_str());
            return false;
         }
      }
   }
   
   for ( map<HString, HXmlTree*>::iterator Iter = GroupMap.begin();
         Iter != GroupMap.end(); Iter++ )
   {
      HString GroupName = Iter->first;
      HXmlTree *GroupXml = Iter->second;
      vector<HXmlTree*> LinkVec;
      GroupXml->GetChildren("LINK", LinkVec);
      for ( int i = 0; i < LinkVec.size(); i++ )
      {
         HString StartJobName = GroupName + JobNameDelimiter + LinkVec[i]->GetPropertyUpper("START_JOB");
         HString EndJobName = GroupName + JobNameDelimiter + LinkVec[i]->GetPropertyUpper("END_JOB");
         job_info &StartJob = Graph[StartJobName];
         job_info &EndJob = Graph[EndJobName];
         if ( StartJob.Type != jtExternJob )
         {
            StartJob.IsBottom = false;
            EndJob.IsTop = false;
         }
      }
   }

   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      for ( int i = 0; i < Job.ExternJobList.size(); i++ )
      {
         HString &ExternJob = Job.ExternJobList[i];
         if ( strstr(ExternJob.c_str(), JobNameDelimiter.c_str()) )
         {
            AddLink(ExternJob, Job.Name);
            AddLink2(ExternJob, Job.Name);
         }
         else
         {
            for ( map<HString, job_info>::iterator Iter2 = Graph.begin();
                  Iter2 != Graph.end(); Iter2++ )
            {
               job_info &Job2 = Iter2->second;
               if ( Job2.GroupId == ExternJob && Job2.IsBottom && Job2.Type != jtExternJob )
               {
                  AddLink(Job2.Name, Job.Name);
                  if ( Job.Type == jtGroup )
                  {
                     AddLink2(Job2.Name, Job.Name);
                  }
               }
            }
            if ( Job.Type != jtGroup )
            {
               Job.OriPreJobs.insert(ExternJob);
            }
         }
      }
   }

   for ( map<HString, HXmlTree*>::iterator Iter = GroupMap.begin();
         Iter != GroupMap.end(); Iter++ )
   {
      HString GroupName = Iter->first;
      HXmlTree *GroupXml = Iter->second;
      vector<HXmlTree*> LinkVec;
      GroupXml->GetChildren("LINK", LinkVec);
      for ( int i = 0; i < LinkVec.size(); i++ )
      {
         HString StartJobName = GroupName + JobNameDelimiter + LinkVec[i]->GetPropertyUpper("START_JOB");
         HString EndJobName = GroupName + JobNameDelimiter + LinkVec[i]->GetPropertyUpper("END_JOB");
         AddLink(StartJobName, EndJobName);
         AddLink2(StartJobName, EndJobName);
      }
   }
   
   for ( map<HString, job_info>::iterator Iter = Graph.begin();
         Iter != Graph.end(); Iter++ )
   {
      job_info &Job = Iter->second;
      Job.InDegree = Job.PreJobs.size();
   }

   HasLinkCache.clear();
   return true;
}

bool HCheckBase::HasLink(const HString &StartJobName, const HString &EndJobName)
{
   HString Name = StartJobName + "," + EndJobName;

   map<HString, bool>::iterator HasLinkIter = HasLinkCache.find(Name);

   if ( HasLinkIter != HasLinkCache.end() )
   {
      return HasLinkIter->second;
   }

   job_info &Job = Graph[StartJobName];

   if ( Job.SuccJobs.find(EndJobName) != Job.SuccJobs.end() )
   {
      HasLinkCache[Name] = true;
      return true;
   }

   for ( set<HString>::iterator Iter = Job.SuccJobs.begin();
         Iter != Job.SuccJobs.end(); Iter++ )
   {
      if ( HasLink(*Iter, EndJobName) )
      {
         HasLinkCache[Name] = true;
         return true;
      }
   }

   HasLinkCache[Name] = false;
   return false;
}

void HCheckBase::GetPreJob(const HString &JobName, const HString &GroupId, vector<HString> &ResultVec, bool WithinGroup)
{
   job_info &Job = Graph[JobName];

   for ( set<HString>::iterator Iter = Job.PreJobs.begin();
         Iter != Job.PreJobs.end(); Iter++ )
   {
      job_info &PreJob = Graph[*Iter];
      if ( ( WithinGroup && PreJob.GroupId != GroupId ) || PreJob.Type == jtGroup )
      {
         continue;
      }
      if ( PreJob.Type == jtConnector )
      {
         GetPreJob(PreJob.Name, GroupId, ResultVec, WithinGroup);
         continue;
      }
      if ( PreJob.Type == jtExternJob )
      {
         ResultVec.push_back(PreJob.ExternJob);
      }
      else
      {
         ResultVec.push_back(PreJob.Name);
      }
   }
}

bool HCheckBase::FindCircuit(const HString &JobName,
   vector<HString> &Circuit)
{
   Circuit.push_back(JobName);

   for ( int i = 0; i < Circuit.size() - 1; i++ )
   {
      if ( JobName == Circuit[i] )
      {
         return true;
      }
   }

   job_info &Job = Graph[JobName];

   for ( set<HString>::iterator Iter = Job.SuccJobs.begin();
         Iter != Job.SuccJobs.end(); Iter++ )
   {
      if ( FindCircuit(*Iter, Circuit) )
      {
         return true;
      }
   }

   Circuit.pop_back();
   return false;
}

void HCheckBase::AddLink(const HString &StartJobName, const HString &EndJobName)
{
   job_info &StartJob = Graph[StartJobName];
   job_info &EndJob = Graph[EndJobName];

   if ( StartJob.Type == jtExternJob )
   {
      if ( strstr(StartJob.ExternJob.c_str(), JobNameDelimiter.c_str()) )
      {
         AddLink(StartJob.ExternJob, EndJobName);
      }
      else
      {
         for ( map<HString, job_info>::iterator Iter = Graph.begin();
               Iter != Graph.end(); Iter++ )
         {
            job_info &Job = Iter->second;
            if ( Job.GroupId == StartJob.ExternJob && Job.IsBottom && Job.Type != jtExternJob )
            {
               AddLink(Job.Name, EndJob.Name);
            }
         }
      }
      return;
   }

   if ( StartJob.Type == jtGroup )
   {
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
            Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.GroupId == StartJob.Group && Job.IsBottom && Job.Type != jtExternJob )
         {
            AddLink(Job.Name, EndJob.Name);
         }
      }
      return;
   }

   if ( EndJob.Type == jtGroup )
   {
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
            Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.GroupId == EndJob.Group && Job.IsTop && Job.Type != jtExternJob )
         {
            AddLink(StartJob.Name, Job.Name);
         }
      }
      return;
   }

   StartJob.SuccJobs.insert(EndJob.Name);
   EndJob.PreJobs.insert(StartJob.Name);
}

void HCheckBase::AddLink2(const HString &StartJobName, const HString &EndJobName)
{
   job_info &StartJob = Graph[StartJobName];
   job_info &EndJob = Graph[EndJobName];

   if ( EndJob.Type == jtGroup )
   {
      for ( map<HString, job_info>::iterator Iter = Graph.begin();
            Iter != Graph.end(); Iter++ )
      {
         job_info &Job = Iter->second;
         if ( Job.GroupId == EndJob.Group && Job.IsTop && Job.Type != jtExternJob )
         {
            AddLink2(StartJob.Name, Job.Name);
         }
      }
      return;
   }

   if ( StartJob.Type == jtExternJob )
   {
      if ( strstr(StartJob.ExternJob.c_str(), JobNameDelimiter.c_str()) )
      {
         AddLink2(StartJob.ExternJob, EndJobName);
      }
      else
      {
         EndJob.OriPreJobs.insert(StartJob.ExternJob);
      }
      return;
   }

   if ( StartJob.Type == jtGroup )
   {
      EndJob.OriPreJobs.insert(StartJob.Group);
      return;
   }

   EndJob.OriPreJobs.insert(StartJob.Name);
}

void LoadBackupInfoFromCfg(HSyslog &Syslog, HXmlTree &Cfg, map<HString, HBackupInfo> &BackupMap)
{
   char ErrMsg[2048 + 1];
   vector<HXmlTree*> NodeVec;

   BackupMap.clear();
   Cfg.GetChildren("BACKUP", NodeVec);

   for ( int i = 0; i < NodeVec.size(); i++ )
   {
      HXmlTree *Node = NodeVec[i];
      HBackupInfo Backup;
      Backup.Name = Node->GetValueUpper("NAME");
      Backup.EnableFlag = Node->GetValueUpper("ENABLE", "FALSE") == "TRUE";
      Backup.PausedFlag = Node->GetValueUpper("PAUSED", "FALSE") == "TRUE";
      Backup.LogFlag = Node->GetValueUpper("LOG", "TRUE") == "TRUE";
      Node->GetValues("CLUSTER", Backup.AddrVec);
      bool ErrorFlag = false;
      for ( int j = 0; j < Backup.AddrVec.size(); j++ )
      {
         HString &AddrStr = Backup.AddrVec[i];
         vector<HString> AddrPort;
         SplitString(AddrStr.c_str(), ':', AddrPort, true);
         if ( AddrPort.size() != 2 )
         {
            snprintf(ErrMsg, 2048, "Invalid backup address(%s)", AddrStr.c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            ErrorFlag = true;
            break;
         }
         if ( j == 0 )
         {
            Backup.Addr = AddrPort[0];
            Backup.Port = StrToInt(AddrPort[1].c_str());
         }
      }
      if ( ErrorFlag )
      {
         continue;
      }
      BackupMap[Backup.Name] = Backup;
   }
}

}; // namespace simbat

extern "C"
{

void *SimbatOpenConfig(char ErrStr[200 + 1])
{
   const char *SimbatBase = getenv("SIMBAT_BASE");

   if ( SimbatBase == NULL )
   {
      snprintf(ErrStr, 200, "SIMBAT_BASE not defined");
      return NULL;
   }

   HXmlTree *XmlTree = new HXmlTree;
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/etc/config.xml", SimbatBase);

   if ( XmlTree->LoadFromFile(FileName, false) )
   {
      return XmlTree;
   }

   snprintf(ErrStr, 200, "Open %s fail, ErrStr = %s", FileName, XmlTree->GetErrorString().c_str());
   return NULL;
}

void SimbatConfigGetValue(void *Handle, const char *NodeName, const char *Tag, char *Result, int MaxLen)
{
   HXmlTree *XmlTree = (HXmlTree*)Handle;
   HXmlTree *Node = NodeName == NULL ? XmlTree : XmlTree->GetChild(NodeName);

   if ( Node == NULL )
   {
      Result[0] = '\0';
      return;
   }

   snprintf(Result, MaxLen, "%s", Node->GetValue(Tag).c_str());
}

void SimbatConfigGetPassword(void *Handle, const char *NodeName, const char *Tag, char *Result, int MaxLen)
{
   HXmlTree *XmlTree = (HXmlTree*)Handle;
   HXmlTree *Node = NodeName == NULL ? XmlTree : XmlTree->GetChild(NodeName);

   if ( Node == NULL )
   {
      Result[0] = '\0';
      return;
   }

   HString EncryptedPassword = Node->GetValue(Tag);

   if ( EncryptedPassword.length() != 64 )
   {
      Result[0] = '\0';
      return;
   }

   char Password[32 + 1];

   BkAscToHex(Password, EncryptedPassword.c_str(), 32);
   BkTeaDecrypt(Password, PASS_KEY, 32);
   Password[32] = '\0';
   BkRightTrim(Password);
   snprintf(Result, MaxLen, "%s", Password);
}

void SimbatCloseConfig(void *Handle)
{
   HXmlTree *XmlTree = (HXmlTree*)Handle;
   delete XmlTree;
}

}
